using System;
using System.Collections.Generic;


namespace Elephant.Framework
{
    /// <summary>
    /// Represents a collection of entities, and provides methods for removal, addition and lookups.
    /// </summary>
    public class EntityCollection : IEntityCollection
    {
        Dictionary<string, List<Entity>> entities = new Dictionary<string, List<Entity>>(); // name, all occurences of entities with this name (case-insensitive)

        List<Entity> allEntities = new List<Entity>();

        bool isReadOnly;

        #region IEntityCollection Members

        /// <summary>
        /// Finds the first occurence of the entity with the specified name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Entity Find(string name)
        {
            if (entities.ContainsKey(name)) {
                foreach (Entity entity in entities[name]) {
                    return entity;
                }
            }

            return null;
        }

        /// <summary>
        /// Finds the first occurence of the entity that contains a component of type T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public Entity FindWith<T>() where T : EntityComponent
        {
            foreach (Entity entity in allEntities) {
                if (entity.Contains<T>()) {
                    return entity;
                }
            }

            return null;
        }

        /// <summary>
        /// Finds all occurences of entities that contains a component of type T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public List<Entity> FindAllWith<T>() where T : EntityComponent
        {
            List<Entity> matches = new List<Entity>();

            foreach (Entity entity in allEntities) {
                if (entity.Contains<T>()) {
                    matches.Add(entity);
                }
            }

            return matches;
        }

        /// <summary>
        /// Finds the first occurence of the component of type T, in the first occurence of the entity with the specified name.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name"></param>
        /// <returns></returns>
        public T Find<T>(string name) where T : EntityComponent
        {
            if (entities.ContainsKey(name)) {
                foreach (Entity entity in entities[name]) {
                    return entity.Find<T>();
                }
            }

            return default(T);
        }

        // virtual, and here because the EntityManager manages what to remove, and when..
        // each entity has a reference to its manager, and they use this method to make sure
        // all of "our" references will be removed entirely 
        public virtual bool Remove(EntityComponent component)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public virtual bool RemoveImmediate(EntityComponent component, bool updateDependencies)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public bool Remove(string name)
        {
            return Remove(Find(name));
        }

        public bool RemoveImmediate(string name)
        {
            return RemoveImmediate(Find(name));
        }

        // overridable so, say, the manager can implement specific functinality for this method
        public virtual bool RemoveImmediate(Entity entity)
        {
            return Remove(entity);
        }

        /// <summary>
        /// Raised when an entity has been added.
        /// </summary>
        public event EventHandler<EntityEventArgs> EntityAdded;
        /// <summary>
        /// Raised when an entity has been added. Override method to handle when an entity has been added.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnEntityAdded(EntityEventArgs e)
        {
            if (EntityAdded != null) {
                EntityAdded(this, e);
            }
        }

        /// <summary>
        /// Raised when an entity has been removed.
        /// </summary>
        public event EventHandler<EntityEventArgs> EntityRemoved;
        /// <summary>
        /// Raised when an entity has been removed. Override method to handle when an entity has been removed.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnEntityRemoved(EntityEventArgs e)
        {
            if (EntityRemoved != null) {
                EntityRemoved(this, e);
            }
        }

        #endregion

        #region ICollection<Entity> Members

        public void Add(Entity item)
        {
            // hack: to avoid using Manager.Add(new entity()), as no manager would be associated with the entity
            if (item.Manager == null) {
                throw new ArgumentException("Cannot add an un-managed entity.");
            }

            if (allEntities.Contains(item)) {
                throw new ArgumentException("Cannot add a reference to an instance that is already in the list.");
            }          

            allEntities.Add(item);

            if (entities.ContainsKey(item.Name)) {
                entities[item.Name].Add(item);
            } else {
                entities.Add(item.Name, new List<Entity>(new Entity[] { item }));
            }

            OnEntityAdded(new EntityEventArgs(item));
        }

        // assuming clear means we want everything gone right away, so we remove immediately
        // update: gotta handle this in ComponentManager
        public virtual void Clear() { }

        public bool Contains(Entity item)
        {
            return allEntities.Contains(item);
        }

        public void CopyTo(Entity[] array, int arrayIndex)
        {
            allEntities.CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get 
            {
                return allEntities.Count; 
            }
        }

        public bool IsReadOnly
        {
            get 
            {
                return isReadOnly;
            }
            set
            {
                isReadOnly = value;
            }
        }

        public virtual bool Remove(Entity item)
        {
            if (item == null) {
                return false;
            }

            bool result = false;

            if (allEntities.Contains(item)) {
                allEntities.Remove(item);
            }

            if (entities.ContainsKey(item.Name)) {
                // check if the list of the specified name already contains this instance
                if (entities[item.Name].Contains(item)) {
                    // if it does, we remove it
                    result = entities[item.Name].Remove(item);

                    OnEntityRemoved(new EntityEventArgs(item));
                }

                // remove the list of entities with this name, since there's noone left
                if (!(entities[item.Name].Count > 0)) {
                    entities.Remove(item.Name);
                }
            }

            return result;
        }

        #endregion

        #region IEnumerable<Entity> Members

        public IEnumerator<Entity> GetEnumerator()
        {
            return allEntities.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return allEntities.GetEnumerator();
        }

        #endregion
    }
}